package io.gitee.zicai.redisson.rpc;

import cn.hutool.core.util.ArrayUtil;
import io.gitee.zicai.redisson.rpc.annotation.RedissonRpcClient;
import io.gitee.zicai.redisson.rpc.annotation.RedissonRpcService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRemoteService;
import org.redisson.api.RedissonClient;
import org.redisson.api.RemoteInvocationOptions;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * RedissonBeanPostProcessor
 *
 * @author zicai
 * @since 2021-03-25
 */
@Slf4j
@Component
public class RedissonBeanPostProcessor implements BeanPostProcessor {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedissonRpcProperties redissonRpcProperties;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 注册RedissonRpcService到redisson
        this.registerRedissonRpcService(bean, beanName);
        return bean;
    }

    private void registerRedissonRpcService(Object bean, String beanName) {
        Class<?> beanClass = getBeanClass(bean);
        RedissonRpcService rpcService = beanClass.getAnnotation(RedissonRpcService.class);
        if (Objects.isNull(rpcService)) {
            return;
        }
        // 注册service到redisson
        this.registerRemoteService(rpcService.interfaceClass(), bean);
        log.info(">>> RedissonRpcService register -> {}", beanName);
    }

    private Class<?> getBeanClass(Object bean) {
        Class<?> beanClass;
        if (ClassUtils.isCglibProxy(bean)) {
            beanClass = ClassUtils.getUserClass(bean);
        } else {
            beanClass = bean.getClass();
        }
        return beanClass;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 自动注入RedissonRpcClient
        this.autowiredRedissonRpcClient(bean, beanName);
        return bean;
    }

    private void autowiredRedissonRpcClient(Object bean, String beanName) {
        Class<?> beanClass = getBeanClass(bean);
        Field[] fields = resolveFields(new Field[]{}, beanClass);
        Arrays.stream(fields)
                .filter(field -> field.isAnnotationPresent(RedissonRpcClient.class))
                .forEach(field -> setRedissonRpcClient(field, bean, beanName));
    }

    private Field[] resolveFields(Field[] fields, Class<?> beanClass) {
        if (Objects.isNull(beanClass) || beanClass == Object.class) {
            return fields;
        }
        Field[] currentFields = beanClass.getDeclaredFields();
        if (ArrayUtil.isNotEmpty(currentFields)) {
            fields = ArrayUtil.addAll(fields, currentFields);
        }
        return resolveFields(fields, beanClass.getSuperclass());
    }

    private void setRedissonRpcClient(Field field, Object bean, String beanName) {
        RedissonRpcClient rpcClient = field.getAnnotation(RedissonRpcClient.class);
        field.setAccessible(true);
        try {
            //noinspection rawtypes
            Class rpcClass = field.getType();
            // 注册client到field
            field.set(bean, getRemoteService(rpcClass, rpcClient));
            log.info(">>> {} autowired RedissonRpcClient -> {}", beanName, rpcClass.getSimpleName());
        } catch (IllegalAccessException e) {
            log.error(">>> {} autowired RedissonRpcClient error -> ", beanName, e);
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void registerRemoteService(Class interfaceClass, Object bean) {
        getRemoteService().register(interfaceClass, bean);
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private Object getRemoteService(Class interfaceClass, RedissonRpcClient rpcClient) {
        // 默认30秒
        long timeout = rpcClient.timeout() > 0 ? rpcClient.timeout() : 30;
        RemoteInvocationOptions options = RemoteInvocationOptions.defaults()
                .expectResultWithin(timeout, TimeUnit.SECONDS)
                .noAck();
        return getRemoteService().get(interfaceClass, options);
    }

    private RRemoteService getRemoteService() {
        return redissonClient.getRemoteService(redissonRpcProperties.getRemoteServiceName());
    }
}
